package cn.yx.common.jpa.service;

import cn.hutool.core.collection.CollectionUtil;
import cn.yx.common.core.exception.CommonException;
import cn.yx.common.core.mapping.DtoDoMapping;
import cn.yx.common.core.pojo.PageDto;
import cn.yx.common.core.pojo.singletable.SingleCond;
import cn.yx.common.core.pojo.singletable.SingleDto;
import cn.yx.common.core.service.BaseService;
import cn.yx.common.core.util.reflect.BeanUtil;
import cn.yx.common.core.util.reflect.ReflectUtil;
import cn.yx.common.jpa.constant.message.BaseServiceMessageKey;
import cn.yx.common.jpa.constant.service.PojoPropertyConstant;
import cn.yx.common.jpa.pojo.SingleDo;
import cn.yx.common.jpa.repository.BaseRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

/**
 * <p>BaseService接口JPA实现类，使用BaseRepository数据访问类</p>
 *
 * @param <Do>   DO类
 * @param <Dto>  DTO类
 * @param <Cond> Cond类
 * @param <Id>   ID主键值类
 * @author Wgssmart
 */
@Slf4j
public abstract class AbstractJpaService<Do extends SingleDo,
        Dto extends SingleDto,
        Cond extends SingleCond,
        Id extends Serializable>
        implements BaseService<Dto, Cond, Id> {

    private final BaseRepository<Do, Id> repository;

    private final DtoDoMapping<Do, Dto> mapping;

    private JpaSpecificationService<Do, Cond> specificationService;

    public AbstractJpaService(BaseRepository<Do, Id> repository, DtoDoMapping<Do, Dto> mapping) {
        this.repository = repository;
        this.mapping = mapping;
    }

    @Autowired
    public void setSpecificationService(JpaSpecificationService<Do, Cond> specificationService) {
        this.specificationService = specificationService;
    }

    @Override
    public boolean save(Dto dto) {
        Do savedDo = repository.save(mapping.dtoToDo(dto));
        return true;
    }

    @Override
    public boolean save(List<Dto> dtoList) {
        List<Do> savedDoList = repository.saveAll(mapping.dtoToDo(dtoList));
        return true;
    }

    @Override
    public boolean save(List<Dto> dtoList, int batchSize) {
        return false;
    }

    @Override
    public boolean saveOrUpdate(Dto dto) {
        Do saveOrUpdateDo = repository.save(mapping.dtoToDo(dto));
        return true;
    }

    @Override
    public boolean saveOrUpdate(Dto dto, Cond updateCond) {
        return false;
    }

    @Override
    public boolean saveOrUpdate(List<Dto> dtoList) {
        List<Do> savedDoList = repository.saveAll(mapping.dtoToDo(dtoList));
        return true;
    }

    @Override
    public boolean saveOrUpdate(List<Dto> dtoList, int batchSize) {
        return false;
    }

    @Override
    public boolean remove(Id id) {
        repository.deleteById(id);
        return true;
    }

    @Override
    public boolean remove(List<Id> idList) {
        repository.deleteAllByIdInBatch(idList);
        return true;
    }

    @Override
    public boolean remove(Cond deleteCond) {
        List<Do> doList = repository.findAll(specificationService.buildCondSpecification(deleteCond));
        repository.deleteAllInBatch(doList);
        return true;
    }

    @Override
    @SuppressWarnings({"unchecked"})
    public boolean update(Dto dto) {
        Optional<Do> doOptional = repository.findById((Id) ReflectUtil.invokeGetter(dto, PojoPropertyConstant.KEY_PROPERTY));
        Do entity = doOptional.orElse(null);
        if (Objects.nonNull(entity)) {
            // 原数据表中字段值拷贝到DTO中值为null的对应字段，保证DTO中的null值不覆盖数据表中原有的值
            Do updateDo = BeanUtil.copyValueToNull(entity, mapping.dtoToDo(dto));
            Do savedDo = repository.save(updateDo);
        }
        return true;
    }

    @Override
    @SuppressWarnings({"unchecked"})
    public boolean update(List<Dto> dtoList) {
        List<Id> idList = new ArrayList<>();
        for (Dto dto : dtoList) {
            idList.add((Id) ReflectUtil.invokeGetter(dto, PojoPropertyConstant.KEY_PROPERTY));
        }
        // 根据所有ID查找数据
        List<Do> doList = repository.findAllById(idList);
        if (CollectionUtil.isNotEmpty(doList)) {
            List<Do> updateDoList = new ArrayList<>();
            for (Do entity : doList) {
                // 通过id筛选出DO对应的DTO
                Dto dto = dtoList.stream().filter(d -> ReflectUtil.invokeGetter(d, PojoPropertyConstant.KEY_PROPERTY).equals(
                        ReflectUtil.invokeGetter(entity, PojoPropertyConstant.KEY_PROPERTY))).findFirst().orElse(null);
                // 原数据表中字段值拷贝到DTO中值为null的对应字段，保证DTO中的null值不覆盖数据表中原有的值
                Do updateDo = BeanUtil.copyValueToNull(entity, mapping.dtoToDo(dto));
                updateDoList.add(updateDo);
            }
            repository.saveAll(updateDoList);
        }
        return true;
    }

    @Override
    public boolean update(List<Dto> dtoList, int batchSize) {
        return false;
    }

    @Override
    public boolean update(Dto dto, Cond updateCond) {
        List<Do> doList = repository.findAll(specificationService.buildCondSpecification(updateCond));
        if (CollectionUtil.isNotEmpty(doList)) {
            List<Do> updateDoList = new ArrayList<>();
            for (Do entity : doList) {
                // 原数据表中字段值拷贝到DTO中值为null的对应字段，保证DTO中的null值不覆盖数据表中原有的值
                Do updateDo = BeanUtil.copyValueToNull(entity, mapping.dtoToDo(dto));
                updateDoList.add(updateDo);
            }
            repository.saveAll(updateDoList);
        }
        return true;
    }

    @Override
    public boolean updateAllColumn(Dto dto) {
        Do updateDo = repository.save(mapping.dtoToDo(dto));
        return true;
    }

    @Override
    public boolean updateAllColumn(List<Dto> dtoList) {
        List<Do> savedDoList = repository.saveAll(mapping.dtoToDo(dtoList));
        return true;
    }

    @Override
    public boolean updateAllColumn(List<Dto> dtoList, int batchSize) {
        return false;
    }

    @Override
    @SuppressWarnings({"unchecked"})
    public boolean updateAllColumn(Dto dto, Cond updateCond) {
        List<Do> doList = repository.findAll(specificationService.buildCondSpecification(updateCond));
        for (Do entity : doList) {
            Id id = (Id) ReflectUtil.invokeGetter(entity, PojoPropertyConstant.KEY_PROPERTY);
            entity = mapping.dtoToDo(dto);
            ReflectUtil.invokeSetter(entity, PojoPropertyConstant.KEY_PROPERTY, id);
        }
        repository.saveAll(doList);
        return true;
    }

    @Override
    public Dto getOne(Id id) {
        Optional<Do> doOptional = repository.findById(id);
        return mapping.doToDto(doOptional.orElse(null));
    }

    @Override
    public Dto getOne(Cond selectCond) {
        Optional<Do> doOptional = repository.findOne(specificationService.buildCondSpecification(selectCond));
        return doOptional.map(mapping::doToDto).orElse(null);
    }

    @Override
    public Dto getOne(Cond selectCond, boolean throwEx) {
        List<Do> doList = repository.findAll(specificationService.buildCondSpecification(selectCond));
        if (throwEx && doList.size() > 1) {
            throw new CommonException(BaseServiceMessageKey.MORE_THAN_ONE_RESULT_FOUND_ERROR);
        }
        return mapping.doToDto(doList.get(0));
    }

    @Override
    public List<Dto> listAll() {
        return mapping.doToDto(repository.findAll());
    }

    @Override
    public List<Dto> listAll(Cond selectCond) {
        return mapping.doToDto(repository.findAll(specificationService.buildCondSpecification(selectCond)));
    }

    @Override
    public List<Dto> listAll(List<Id> idList) {
        return mapping.doToDto(repository.findAllById(idList));
    }

    @Override
    public PageDto<Dto> page(Cond selectCond) {
        Integer pageNum = ReflectUtil.invokeGetter(selectCond, PojoPropertyConstant.PAGE_NUM_PROPERTY, Integer.class);
        Integer pageSize = ReflectUtil.invokeGetter(selectCond, PojoPropertyConstant.PAGE_SIZE_PROPERTY, Integer.class);
        Page<Do> page = repository.findAll(specificationService.buildCondSpecification(selectCond),
                PageRequest.of(pageNum, pageSize));
        PageDto<Dto> pageDto = new PageDto<>();
        pageDto.setTotal(page.getTotalElements());
        pageDto.setResults(mapping.doToDto(page.toList()));
        return pageDto;
    }

    @Override
    public long count() {
        return repository.count();
    }

    @Override
    public long count(Cond selectCond) {
        return repository.count(specificationService.buildCondSpecification(selectCond));
    }
}